#include <tinx/debug.h>
#include <tinx/io.h>
#include <tinx/memory.h>
#include <tinx/acpi.h>
#include <tinx/string.h>
#include <tinx/stdlib.h>

static u32 SMI_CMD;
static u8 ACPI_ENABLE;
static u8 ACPI_DISABLE;
static u32 PM1a_CNT;
static u32 PM1b_CNT;
static u32 PM1_EVT_LEN;
static u16 SLP_TYPa;
static u16 SLP_TYPb;
static u16 SLP_EN;
static u16 SCI_EN;
static u16 SCI_Interrupt;
static u64 reset_reg_address;
static u8 reset_value;
static u32 flags;

static ACPI_RSDT *rsdt;

static RSDPtr *acpi_check_rsdp(u32 *addr)
{
    RSDPtr *rsdp = NULL;
    char *sig = "RSD PTR ";
    u8 check = 0;
    u8 *ptr = (u8 *)addr;

    if (!memcmp(addr, sig, 8))
    {
        for (size_t i = 0; i < sizeof(RSDPtr); i++)
        {
            check += *ptr++;
        }
        if (!check)
        {
            rsdp = (RSDPtr *)addr;
        }
    }
    return rsdp;
}

// 寻找 RSDP
static RSDPtr *acpi_find_rsdp()
{
    u32 addr;
    RSDPtr *rsdp = NULL;

    for (addr = 0xE0000; addr < 0x100000; addr += 0x10)
    {
        rsdp = acpi_check_rsdp((u32 *)addr);
        if (!rsdp)
        {
            continue;
        }
        return rsdp;
    }

    u32 ebda = *((u16 *)0x40E);
    ebda = ebda * 0x10 & 0xFFFFF;

    RSDPtr *ptr = NULL;
    rsdp = NULL;

    for (addr = ebda; addr < ebda + 1024; addr += 0x10)
    {
        ptr = acpi_check_rsdp((u32 *)addr);
        if (!ptr)
        {
            continue;
        }
        rsdp = ptr;
        break;
    }

    return rsdp;
}

// 映射一个 ACPI 表
void acpi_map_table(ACPISDTHeader *header)
{
    u32 page = PAGE(IDX((u32)header));
    u32 offset = (u32)header & 0xfff;

    // 先映射 header，访问 header->Length
    map_area(page, offset + sizeof(ACPISDTHeader));

    int len = header->Length;
    len += offset;
    // 重复映射的会跳过
    map_area((u32)page, len);
}

// 寻找 ACPI 表
ACPISDTHeader *acpi_find_table(char *name)
{
    int len = (rsdt->header.Length - sizeof(ACPI_RSDT)) / sizeof(u32);
    u32 *ptr = (u32 *)((u32)rsdt + sizeof(ACPISDTHeader));

    ACPISDTHeader *hptr;
    ACPISDTHeader *header = NULL;

    for (size_t i = 0; i < len; i++)
    {
        hptr = (ACPISDTHeader *)*ptr;
        if (!memcmp(hptr->Signature, name, 4))
        {
            header = hptr;
            break;
        }
        ptr++;
    }

    acpi_map_table(header);

    return header;
}

void acpi_enable()
{
    // 如果 ACPI 已经开启, 就退出
    if (inw(PM1a_CNT) & SCI_EN)
    {
        return;
    }
    // 检查 ACPI 能否开启
    if (!SMI_CMD || !ACPI_ENABLE)
    {
        return;
    }

    // 发送 开启 ACPI 的命令
    outb(SMI_CMD, ACPI_ENABLE);

    // 用一些时间开启 ACPI
    int i;
    for (i = 0; i < 300; i++)
    {
        if (inw(PM1a_CNT) & SCI_EN)
        {
            break;
        }
    }

    if (!PM1b_CNT)
        return;

    // 等待
    for (; i < 300; i++)
    {
        if (inw(PM1b_CNT) & SCI_EN)
        {
            break;
        }
    }
}

void acpi_init()
{
    rsdt = (ACPI_RSDT *)acpi_find_rsdp()->RsdtAddress;
    acpi_map_table(&rsdt->header);

    ACPI_FADT *fadt = (ACPI_FADT *)acpi_find_table("FACP");

    acpi_map_table((ACPISDTHeader *)fadt->Dsdt);

    // 寻找 S5
    u8 *S5Addr = (char *)fadt->Dsdt + 36;
    u32 length = ((ACPISDTHeader *)fadt->Dsdt)->Length - sizeof(ACPISDTHeader);

    while (length--)
    {
        if (!memcmp(S5Addr, "_S5_", 4))
        {
            break;
        }
        S5Addr++;
    }

    if ((*(S5Addr - 1) != 0x08 &&
        (*(S5Addr - 1) != '\\' || *(S5Addr - 2) != 0x08))
        || *(S5Addr + 4) != 0x12)
        return;

    // 跳过一些数据
    S5Addr += 5;
    S5Addr += ((*S5Addr & 0xc0) >> 6) + 2;

    // 获得一些数据
    if (*S5Addr == 0x0A)
        S5Addr++;

    SLP_TYPa = *S5Addr << 10;
    S5Addr++;

    if (*S5Addr == 0x0A)
        S5Addr++;

    SLP_TYPb = *S5Addr << 10;

    SMI_CMD = fadt->SMI_CommandPort;

    ACPI_ENABLE = fadt->AcpiEnable;
    ACPI_DISABLE = fadt->AcpiDisable;

    PM1a_CNT = fadt->PM1aControlBlock;
    PM1b_CNT = fadt->PM1bControlBlock;

    SLP_EN = 1 << 13;
    SCI_EN = 1;

    PM1_EVT_LEN = fadt->PM1EventLength;

    SCI_Interrupt = fadt->SCI_Interrupt;

    reset_reg_address = fadt->ResetReg.Address;
    reset_value = fadt->ResetValue;

    flags = fadt->Flags;

    // 开启 ACPI
    acpi_enable();
}

int sys_shutdown()
{
    if (!SCI_EN)
        return EOF;

    // 发送关机命令
    outw(PM1a_CNT, SLP_TYPa | SLP_EN);
    if (PM1b_CNT)
        outw(PM1b_CNT, SLP_TYPb | SLP_EN);
}

int sys_reboot()
{
    while (true)
    {
        // 通过 ACPI reset 寄存器重启
        // 仅限 ACPI 2.0+ 且设置了flags第十位
        if (flags & (1 << 10))
            outb(reset_reg_address, reset_value);

        // 第一种不行使用第二种方式
        // 通过键盘控制器重启
        while (inb(0x64) & 0x3)
            inb(0x60);
        outb(0x64, 0xfe);
    }
}